﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;

namespace TheVoiceOfSnake
{
    class MFCCHandler
    {
        private int F;      // częstotliwość próbkowania; ilość próbek/s
        private int FFTSize;
        private List<List<double>> mel;
        private int numOfFilters;
        private int[] melstart;
        private int[] mellength;

        private int maxFreq = 10000;//10kHz

        public void ComputeModuleValues(IEnumerable<Frame> frames) 
        {
            foreach (var frame in frames) 
            {
                for (int i = 0; i < frame.Size(); i++)
                {
                    frame[i] = Math.Sqrt(Math.Pow(frame.GetRealPart(i), 2.0) + Math.Pow(frame.GetImagPart(i), 2.0));
                }
            }
        }

        public IEnumerable<double> ExtractFinalParameters(IEnumerable<Frame> frames, int freq) 
        {
            List<double> result = new List<double>();
            SetConstans(frames, freq);
            SetMelFilters(frames);
            result.AddRange(CalculateMel(frames));
            
            /* wersja z DCT */
            return DCT(result);

            /* wersja z IFFT */
            //return result;
        }

        private void SetConstans(IEnumerable<Frame> frames, int freq)
        {
            F = freq;
            FFTSize = frames.First().Size();
            //numOfFilters = FFTSize / sizeof(double);
            numOfFilters = 16;//(frames.Count() * GetObjectSize(frames.First().GetTableOfValues())) / GetObjectSize(frames.First().GetTableOfValues());
            mel = new List<List<double>>();
            melstart = new int[numOfFilters];
            mellength = new int[numOfFilters];
        }

        public void SetMelFilters(IEnumerable<Frame> frames) 
        {
            double fmax;
            double dphi;
            double fsample;
            double freq;
            double[] temp = new double[FFTSize/2];

            fmax = 2595 * Math.Log10(F/700 + 1);    // max częstotliwość 
            dphi = fmax / numOfFilters+1;
            freq = (double)F / FFTSize;     // prążek w analizie FFT
            for (int i = 0; i < numOfFilters; i++)
            {
                melstart[i] = FFTSize / 2;
                mellength[i] = 0;
                //memset(temp, 0, sizeof(double) * fft_size / 2);

                for (int j = 0; (j < FFTSize / 2); j++)
                {
                    //mel[i].reserve(mellength[i]);
                    fsample = 2595 * Math.Log10(freq * j / 700 + 1);

                    if ((dphi * i <= fsample) && (fsample < dphi * (i + 1))) temp[j] = (fsample - dphi * i) / (dphi * (i + 1) - dphi * i);
                    if ((dphi * (i + 1) <= fsample) && (fsample < dphi * (i + 2))) temp[j] = (fsample - dphi * (i + 2)) / (dphi * (i + 1) - dphi * (i + 2));
                    if ((temp[j] != 0) && (melstart[i] > j)) melstart[i] = j;
                    if (temp[j] != 0) mellength[i]++;
                }

                //mel[i].reserve(mellength[i]);
                //list.Add(temp[melstart[i]] + mellength[i]);
                List<double> list = new List<double>();
                list.AddRange(AddElements(melstart[i], melstart[i]+mellength[i], temp));
                mel.Add(list);
                //mel[i].assign(&temp[melstart[i]], &temp[melstart[i]] + mellength[i]);
            }
        }

        private int GetObjectSize(object TestObject)
        {
            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            byte[] Array;
            bf.Serialize(ms, TestObject);
            Array = ms.ToArray();
            return Array.Length;
        }

        private IEnumerable<double> AddElements(int start, int end, double[] temp) 
        {
            for (int i = start; i < end; i++)
            {
                yield return temp[i];
            }
        }

        private IEnumerable<double> CalculateMel(IEnumerable<Frame> frames)
        {
            double tempResult = 0;
            double[] tempTab;
            foreach (var frame in frames) 
            {
                tempTab = frame.GetTableOfValues();
                for (int i = 0; (i < 16); i++)
                {
                    for (int j = 0; j < mellength[i]; j++)
                    {
                        tempResult += mel[i][j] * tempTab[j + melstart[i]];
                    }
                    yield return Math.Log(tempResult);
                }
            }
        }

        public IEnumerable<double> DCT(List<double> x)
        {
            int N = x.Count;
            double temp;
            double averageFrameValue = 0;
            for(int i=0, counter = 1; i < N; i++)
            {
                temp = 0.0;
                for(int n = 0; n < N; n++)
                {
                    temp = (temp + x[n] * Math.Cos((Math.PI / N) * (n - .5) * i));
                }
                //yield return temp;
                // uśredniania wartości filtrów mel dla ramki
                if (counter % numOfFilters == 0)
                {
                    yield return (averageFrameValue/numOfFilters);
                    averageFrameValue = 0;
                }
                else 
                {
                    averageFrameValue += temp;
                }
                counter++;
            }
        }
    }
}
